package androidx.compose.ui.platform

import androidx.compose.ui.input.key.Key
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.ImeOptions
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import kotlinx.browser.document
import kotlinx.browser.window
import org.w3c.dom.HTMLElement
import org.w3c.dom.events.CompositionEvent
import org.w3c.dom.events.KeyboardEvent
import org.w3c.dom.events.UIEvent

internal class DomInputStrategy(
    imeOptions: ImeOptions,
    private val composeSender: ComposeCommandCommunicator,
) {
    val htmlInput = imeOptions.createDomElement()

    private var lastMeaningfulUpdate = TextFieldValue("")

    init {
        initEvents()
    }

    private val nativeInputEventsProcessor = object : NativeInputEventsProcessor(composeSender) {
        override fun scheduleCheckpoint() {
            window.requestAnimationFrame {
                runCheckpoint(currentTextFieldValue = lastMeaningfulUpdate)
            }
        }
    }

    fun updateState(textFieldValue: TextFieldValue) {
        htmlInput as HTMLElementWithValue

        if (lastMeaningfulUpdate.text != textFieldValue.text) {
            htmlInput.value = textFieldValue.text
        }
        if (lastMeaningfulUpdate.selection != textFieldValue.selection) {
            htmlInput.setSelectionRange(textFieldValue.selection.min, textFieldValue.selection.max)
        }

        lastMeaningfulUpdate = textFieldValue
    }

    private val tabKeyCode = Key.Tab.keyCode.toInt()

    private fun initEvents() {
        htmlInput.addEventListener("blur", { evt ->
            // TODO: any actions here?
        })

        htmlInput.addEventListener("keydown", { evt ->
            nativeInputEventsProcessor.registerEvent(evt as KeyboardEvent)

            if (evt.keyCode == tabKeyCode) {
                // Compose logic will handle the focus movement or insert Tabs if necessary
                evt.preventDefault()
            }
        })

        htmlInput.addEventListener("keyup", { evt ->
            nativeInputEventsProcessor.registerEvent(evt as KeyboardEvent)
        })

        htmlInput.addEventListener("beforeinput", { evt ->
            if (evt is InputEvent) {
                htmlInput as HTMLElementWithValue
                val deleteContentBackwardSize = htmlInput.selectionEnd - htmlInput.selectionStart

                evt.deleteContentBackwardSize = deleteContentBackwardSize
                nativeInputEventsProcessor.registerEvent(evt)
            }
        })

        htmlInput.addEventListener("compositionstart", { evt ->
            nativeInputEventsProcessor.registerEvent(evt as CompositionEvent)
        })

        htmlInput.addEventListener("compositionend", { evt ->
            nativeInputEventsProcessor.registerEvent(evt as CompositionEvent)
        })
    }
}

internal external class InputEvent : UIEvent {
    val inputType: String
    val data: String?
    val isComposing: Boolean
    var deleteContentBackwardSize: Int
}

private fun ImeOptions.createDomElement(): HTMLElement {
    val htmlElement = document.createElement(
        if (singleLine) "input" else "textarea"
    ) as HTMLElement

    htmlElement.setAttribute("autocorrect", "off")
    htmlElement.setAttribute("autocomplete", "off")
    htmlElement.setAttribute("autocapitalize", "off")
    htmlElement.setAttribute("spellcheck", "false")

    val inputMode = when (keyboardType) {
        KeyboardType.Text -> "text"
        KeyboardType.Ascii -> "text"
        KeyboardType.Number -> "number"
        KeyboardType.Phone -> "tel"
        KeyboardType.Uri -> "url"
        KeyboardType.Email -> "email"
        KeyboardType.Password -> "password"
        KeyboardType.NumberPassword -> "number"
        KeyboardType.Decimal -> "decimal"
        else -> "text"
    }

    val enterKeyHint = when (imeAction) {
        ImeAction.Default -> "enter"
        ImeAction.None -> "enter"
        ImeAction.Done -> "done"
        ImeAction.Go -> "go"
        ImeAction.Next -> "next"
        ImeAction.Previous -> "previous"
        ImeAction.Search -> "search"
        ImeAction.Send -> "send"
        else -> "enter"
    }

    htmlElement.setAttribute("inputmode", inputMode)
    htmlElement.setAttribute("enterkeyhint", enterKeyHint)


    htmlElement.style.apply {
        setProperty("position", "absolute")
        setProperty("user-select", "none")
        setProperty("forced-color-adjust", "none")
        setProperty("white-space", "pre")
        setProperty("align-content", "center")
        setProperty(
            "top",
            "calc(min(var(--compose-internal-web-backing-input-top) * 1px, 100vh - var(--compose-internal-web-backing-input-height) * 1px))"
        )
        setProperty(
            "left",
            "calc(min(var(--compose-internal-web-backing-input-left) * 1px, 100vw - var(--compose-internal-web-backing-input-width) * 1px))"
        )
        setProperty("width", "calc(var(--compose-internal-web-backing-input-width) * 1px")
        setProperty("height", "calc(var(--compose-internal-web-backing-input-height) * 1px")
        setProperty("padding", "0")
        setProperty("color", "transparent")
        setProperty("background", "transparent")
        setProperty("caret-color", "transparent")
        setProperty("outline", "none")
        setProperty("border", "none")
        setProperty("resize", "none")
        setProperty("text-shadow", "none")
        setProperty("z-index", "-1")
        // TODO: do we need pointer-events: none
        //setProperty("pointer-events", "none")

        // I keep "opacity" commented to make it explicit that we can't use this property.
        // Reason: Safari iOS keyboard overlaps the text input. See CMP-8611
        // setProperty("opacity", "0")

        // To prevent auto-zoom in some mobile browsers, we set a larger font-size
        setProperty("font-size", "20px")
    }

    return htmlElement
}

private external interface HTMLElementWithValue {
    var value: String
    val selectionStart: Int
    val selectionEnd: Int
    val selectionDirection: String?
    fun setSelectionRange(start: Int, end: Int, direction: String = definedExternally)
}

internal fun isTypedEvent(evt: KeyboardEvent): Boolean =
    js("!evt.metaKey && !evt.ctrlKey && evt.key.charAt(0) === evt.key")